#include "SgwGapProxy.h"
#include <stdint.h>
#include "Trace.h"
#include "GapGenerator.h"
#include "GAP.pb.h"
#include <string>
#include "GapEventConnectCfm.h"
#include "GapEventConnectRej.h"
#include "GapEventEchoReq.h"
#include "GapEventMediaBearerSetupReq.h"
#include "GapEventMediaBearerRelease.h"
#include "GapEventT709Expired.h"
#include "GapEventT711Expired.h"


namespace tsc
{
namespace mgw
{

using namespace GatewayApplicationPart;
using ProtocolsCommon::PlayableItem;
using namespace tsc::common;

SgwGapProxy* SgwGapProxy::mInstance = 0;

/*******************************************************************************
 *
 * Constructor
 *
 ******************************************************************************/
SgwGapProxy::SgwGapProxy(RequestHandler* rh) :
        mpReqHandler(rh)
{
    INFO("Creating SgwGapProxy in MediaGateway");
} // SgwGapProxy::SgwGapProxy

/*******************************************************************************
 *
 * Destructor
 *
 ******************************************************************************/
SgwGapProxy::~SgwGapProxy()
{

}

//===================================================================
void SgwGapProxy::init(RequestHandler* rh)
{
    if(!mInstance)
    {
        mInstance = new SgwGapProxy(rh);
    }
}

//===================================================================
SgwGapProxy&
SgwGapProxy::instance()
{
	if(!mInstance)
	{
	    TRACE_ERROR("Trying to get instance of a proxy before initialization");
	}
	return *mInstance;
}

//===================================================================
void
SgwGapProxy::handlePdu(Message*& pMessage)
{
    if(!pMessage)
    {
        TRACE(1, "Received NULL Message - ignoring");
        return;
    }

    GapPdu* gapPdu = new GapPdu();
    if(!gapPdu->ParseFromArray(pMessage->getData(), pMessage->getDataSize()))
    {
        TRACE(1, "Unable to parse received data to GapPdu");

        DELETE_MESSAGE_P(pMessage);
        delete gapPdu;
        return;
    }
    if(!gapPdu->IsInitialized())
    {
        TRACE(1, STR("Received invalid GapPdu, errorStr: %s",
                gapPdu->InitializationErrorString().data()));

        DELETE_MESSAGE_P(pMessage);
        delete gapPdu;
        return;
    }

    handleGapPdu(gapPdu);

    DELETE_MESSAGE_P(pMessage);

    if(gapPdu)
    {
        TRACE_ERROR(STR("GapPdu with PduNum=0x%x was not handled", gapPdu->pdunumber()));
        delete gapPdu;
        gapPdu = 0;
    }
}

//===================================================================
void
SgwGapProxy::handleGapPdu(GapPdu*& gapPdu)
{
	common::EventData* event = 0;
	if(GapGenerator::instance().getMgwId() != gapPdu->mgwid())
	{
	    TRACE_ERROR(STR("Received data for mgwId=%u, while this MGW ID=%u",
                        gapPdu->mgwid(),
                        GapGenerator::instance().getMgwId()));
	    delete gapPdu;
	    gapPdu = 0;
	    return;
	}

	switch(gapPdu->pdunumber())
	{
        case GAP_PDU_CONNECT_CFM:
        {
            GapConnectCfm connCfm;
            if(!connCfm.ParseFromString(gapPdu->pdu()))
            {
                TRACE(1, "Received invalid GapConnectCfm, discarding");
                delete gapPdu;
                gapPdu = 0;
                break;
            }

            event = new GapEventConnectCfm(connCfm);

        }
        break;

        case GAP_PDU_CONNECT_REJ:
        {
            GapConnectRej connRej;
            if(!connRej.ParseFromString(gapPdu->pdu()))
            {
                TRACE(1, "Received invalid GapConnectRej, discarding");
                break;
            }

            event = new GapEventConnectRej(connRej);

        }
        break;

        case GAP_PDU_ECHO_REQ:
        {
            GapEchoReq echoReq;
            if(!echoReq.ParseFromString(gapPdu->pdu()))
            {
                TRACE(1, "Received invalid GapEchoReq, discarding");
                break;
            }

            event = new GapEventEchoReq(echoReq);
        }
        break;

		case GAP_PDU_MEDIA_BEARER_SETUP_REQ:
		{
		    GapMediaBearerSetupRequest bearerSetupReq;
            if(!bearerSetupReq.ParseFromString(gapPdu->pdu()))
            {
                TRACE(1, "Received invalid GapMediaBearerSetupRequest, discarding");
                break;
            }

            event = new GapEventMediaBearerSetupReq(bearerSetupReq);
		}
		break;

		case GAP_PDU_MEDIA_BEARER_RELEASE:
		{
		    GapMediaBearerRelease bearerRel;
            if(!bearerRel.ParseFromString(gapPdu->pdu()))
            {
                TRACE(1, "Received invalid GapMediaBearerRelease, discarding");
                break;
            }

            event = new GapEventMediaBearerRelease(bearerRel);
		}
		break;

		case GAP_PDU_TIMER:
        {
            ProtocolsCommon::TimerExpired timerExp;
            if (!timerExp.ParseFromString(gapPdu->pdu()))
            {
                TRACE(1, STR("Received invalid TimerExpired message, errorStr: %s",
                             timerExp.InitializationErrorString().data()));

                break;
            }

            TRACE(4, STR("Received TimerExpired, timerId=%u", timerExp.timerid()));

            if(timerExp.timerid() == GAP_TIMER_ID_T709)
            {
                event = new GapEventT709Expired();
            }
            else if(timerExp.timerid() == GAP_TIMER_ID_T711)
            {
                event = new GapEventT711Expired();
            }
            else
            {
                TRACE(1, STR("Unexpected timerId=%u", timerExp.timerid()));
            }
        }
        break;

		default:
			TRACE(1, STR("Received unknown GapPdu with pduNum=0x%x",
			             gapPdu->pdunumber()));
	}

	if(event)
	{
	    GapGenerator::instance().injectEvent(event);

		// check if event was handled
		if(event)
		{
		    TRACE_ERROR(STR("Event:sigNo=0x%x not handled", event->sigNo()));
			delete event;
		}
	}
	if(gapPdu)
	{
		delete gapPdu;
		gapPdu = 0;
	}

}

//===================================================================
bool
SgwGapProxy::sendGapConnectionInd(
    GapGenerator& gapGen)
{
    GapConnectInd connInd;

    // TODO how obtain our IP address ??
    // take from RequestHandler
    ProtocolsCommon::IpAddress mgwIp;

    connInd.set_allocated_mgwaddress(&mgwIp);

    if(!connInd.IsInitialized())
    {
        TRACE(1, STR("Failed to encode GapConnectInd, errorStr: %s",
                connInd.InitializationErrorString().c_str()));
        return false;
    }

    if(!sendGapPdu(GAP_MGW_INITIAL_ID,
                   connInd.SerializeAsString(),
                   GAP_PDU_CONNECT_IND))
    {
        TRACE(1, "Failed to send GapConnectInd");
        return false;
    }
    TRACE(6, "GapConnectInd sent");
    return true;
}

//===================================================================
bool
SgwGapProxy::sendGapEchoResp(GapGenerator& gapGen)
{
    GapEchoResponse echoResp;

    if(!echoResp.IsInitialized())
    {
        TRACE(1, STR("Failed to encode GapEchoResponse, errorStr: %s",
                echoResp.InitializationErrorString().c_str()));
        return false;
    }

    if(!sendGapPdu(gapGen.getMgwId(),
                   echoResp.SerializeAsString(),
                   GAP_PDU_ECHO_RES))
    {
        TRACE(1, "Failed to send GapEchoResponse");
        return false;
    }
    TRACE(6, STR("GapEchoResponse sent, mgwId=0x%x",
                 gapGen.getMgwId()));
    return true;
}

//===================================================================
bool
SgwGapProxy::sendGapMediaBearerSetupCfm(
    GapGenerator& gapGen,
    uint32_t connectionId,
    uint32_t serverMediaPort)
{
    GapMediaBearerSetupConfirm setupCfm;
    setupCfm.set_connectionid(connectionId);
    setupCfm.set_servermediaport(serverMediaPort);

    if(!setupCfm.IsInitialized())
    {
        TRACE(1, STR("Failed to encode GapMediaBearerSetupConfirm, errorStr: %s",
                     setupCfm.InitializationErrorString().c_str()));
        return false;
    }

    if(!sendGapPdu(gapGen.getMgwId(),
                   setupCfm.SerializeAsString(),
                   GAP_PDU_MEDIA_BEARER_SETUP_CFM))
    {
        TRACE(1, "Failed to send GapMediaBearerSetupConfirm");
        return false;
    }
    TRACE(6, STR("GapMediaBearerSetupConfirm sent, mgwId=0x%x, connId=0x%x, "
                 "serverMediaPort=%u",
                 gapGen.getMgwId(),
                 connectionId,
                 serverMediaPort));
    return true;
}

//===================================================================
bool
SgwGapProxy::sendGapMediaBearerSetupRej(
    GapGenerator& gapGen,
    uint32_t connectionId,
    GatewayApplicationPart::Cause cause)
{
    GapMediaBearerSetupReject setupRej;
    setupRej.set_connectionid(connectionId);
    setupRej.set_cause(cause);

    if(!setupRej.IsInitialized())
    {
        TRACE(1, STR("Failed to encode GapMediaBearerSetupReject, errorStr: %s",
                     setupRej.InitializationErrorString().c_str()));
        return false;
    }

    if(!sendGapPdu(gapGen.getMgwId(),
                   setupRej.SerializeAsString(),
                   GAP_PDU_MEDIA_BEARER_SETUP_REJ))
    {
        TRACE(1, "Failed to send GapMediaBearerSetupReject");
        return false;
    }
    TRACE(6, STR("GapMediaBearerSetupReject sent, mgwId=0x%x, connId=0x%x, "
                 "cause=%u",
                 gapGen.getMgwId(),
                 connectionId,
                 cause));
    return true;
}

//===================================================================
bool
SgwGapProxy::sendGapTransmissionFinishedInd(
    GapGenerator& gapGen,
    uint32_t connectionId)
{
    GapMediaTransmissionFinishedInd transFinishedInd;
    transFinishedInd.set_connectionid(connectionId);

    if(!transFinishedInd.IsInitialized())
    {
        TRACE(1, STR("Failed to encode GapMediaTransmissionFinishedInd, errorStr: %s",
                     transFinishedInd.InitializationErrorString().c_str()));
        return false;
    }

    if(!sendGapPdu(gapGen.getMgwId(),
                   transFinishedInd.SerializeAsString(),
                   GAP_PDU_MEDIA_TRANSMISSION_END))
    {
        TRACE(1, "Failed to send GapMediaTransmissionFinishedInd");
        return false;
    }
    TRACE(6, STR("GapMediaTransmissionFinishedInd sent, mgwId=0x%x, connId=0x%x",
                 gapGen.getMgwId(),
                 connectionId));
    return true;
}

//===================================================================
bool
SgwGapProxy::sendGapPdu(
    uint32_t           mgwId,
    const std::string& msg,
    GapPduNumber       pduNum)
{
	GapPdu gapPdu;
	gapPdu.set_mgwid(mgwId);
	gapPdu.set_pdu(msg);
	gapPdu.set_pdunumber(pduNum);

	if(!gapPdu.IsInitialized())
	{
	    TRACE_ERROR(STR("Failed to encode GapPdu with pduNum=0x%x",
	                    pduNum));
		return false;
	}

	std::string encodedGapPdu;
	gapPdu.SerializeToString(&encodedGapPdu);

	Message* pMessage = new Message(encodedGapPdu.data(),
                                    encodedGapPdu.size(),
                                    GapGenerator::instance().getSocketConnectionId());

	// send the message
    if(mpReqHandler->putRequestOnOutQueue(pMessage) == REQUEST_IS_ON_QUEUE)
    {
        // request handler claims ownership of the message
        pMessage = 0;
        return true;
    }
    return false;
}

common::RequestHandler*
SgwGapProxy::getRequestHandler()
{
    return mpReqHandler;
}

} // namespace mgw
} // namespace tsc
